using System;
using System.Diagnostics;
using System.Collections.Generic;

namespace OpenRay
{
	//Immutable Class
	public abstract class TriangleGeometry : Geometry
	{	
		private readonly Vector surfaceNormal;
		private readonly int uAxis; //, vAxis;
		
		public TriangleGeometry ( IList<Point> points, IList<Vector> n, IList<Material> m )
			: base( points, n, m )
		{
			Debug.Assert( points.Count == 3, "Incorrect number of points provided" );			
			surfaceNormal = Vector.Normalize( Vector.CrossProduct( points[1] - points[0], points[2] - points[0] ) );
			
			int dominantAxis;
			if ( Math.Abs( surfaceNormal.X ) > Math.Abs( surfaceNormal.Y ) )
			{
				if ( Math.Abs( surfaceNormal.X ) > Math.Abs( surfaceNormal.Z ) )
				{
					dominantAxis = 0;	
				} else {
					dominantAxis = 2;	
				}
			} else {
				if ( Math.Abs( surfaceNormal.Y ) > Math.Abs( surfaceNormal.Z ) )
				{
					dominantAxis = 1;
				} else {
					dominantAxis = 2;	
				}
			}
			
			uAxis = ( dominantAxis + 1 ) % 3;
			//vAxis = ( dominantAxis + 2 ) % 3;
		}
		
		public override IList<GeometryHit> Trace(Ray r)
		{
			List<GeometryHit> ret = new List<GeometryHit>(1);
			
			//Distance to plane of triangle
			Vector temp = r.Origin - vertices[0];
			float distance = -1.0f * Vector.DotProduct( temp, surfaceNormal ) / Vector.DotProduct( r.Direction, surfaceNormal );
			
			if ( distance < Delta )
				return ret;
			
			//Use Barycentric Coordinates to calculate if ray hits triangle
			Point modelHit = r.Origin + r.Direction * distance;
			
			/*
			Vector v0 = vertices[2] - vertices[0];
			Vector v1 = vertices[1] - vertices[0];
			Vector v2 = modelHit - vertices[0];
			
			float dot00 = Vector.DotProduct( v0, v0 );
			float dot01 = Vector.DotProduct( v0, v1 );
			float dot02 = Vector.DotProduct( v0, v2 );
			float dot11 = Vector.DotProduct( v1, v1 );
			float dot12 = Vector.DotProduct( v1, v2 );
			
			float invDenom = 1.0f / ( dot00 * dot11 - dot01 * dot01 );
			
			float u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom;
			float v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom;
			*/
			
			Vector b = vertices[1] - vertices[0];
			Vector c = vertices[2] - vertices[0];
			Vector p = modelHit - vertices[0];
			
			float u = 0, v = 0;
			
			switch ( uAxis ) {
			case 0:
				u = ( p.Y * c.X - p.X * c.Y ) /
				    ( b.Y * c.X - b.X * c.Y );
				
				v = ( p.Y * b.X - p.X * b.Y ) /
				    ( c.Y * b.X - c.X * b.Y );
				break;
			case 1:
				u = ( p.Z * c.Y - p.Y * c.Z ) /
				    ( b.Z * c.Y - b.Y * c.Z );
				
				v = ( p.Z * b.Y - p.Y * b.Z ) /
				    ( c.Z * b.Y - c.Y * b.Z );
				break;
			case 2:
				u = ( p.X * c.Z - p.Z * c.X ) /
				    ( b.X * c.Z - b.Z * c.X );
				
				v = ( p.X * b.Z - p.Z * b.X ) /
				    ( c.X * b.Z - c.Z * b.X );
				break;
			}
			
			if ( u >= 0.0f - Delta && v >= 0.0f - Delta && u + v <= 1.0f + Delta ) {				
				ret.Add( new GeometryHit( this, r, distance, new InterpolationData( 1.0f - u - v, u, v ) ) );
			}
			
			return ret;
		}
		
		public override BoundingBox BoundingBox ()
		{
			Point min = new Point( Math.Min( Math.Min( vertices[0].X, vertices[1].X ), vertices[2].X ),
			                       Math.Min( Math.Min( vertices[0].Y, vertices[1].Y ), vertices[2].Y ),
			                       Math.Min( Math.Min( vertices[0].Z, vertices[1].Z ), vertices[2].Z ) );
			
			Point max = new Point( Math.Max( Math.Max( vertices[0].X, vertices[1].X ), vertices[2].X ),
                        	  	   Math.Max( Math.Max( vertices[0].Y, vertices[1].Y ), vertices[2].Y ),
                        		   Math.Max( Math.Max( vertices[0].Z, vertices[1].Z ), vertices[2].Z ) );
			
			return new BoundingBox( min, max );
		}
	}
}

